﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Media3D;
using System.Windows.Shapes;
using System.Windows.Threading;
using Grafika_WPF.Components;
using Grafika_WPF.Shapes;
using Camera = Grafika_WPF.Components.Camera;
using Light = Grafika_WPF.Components.Light;

namespace Grafika_WPF.Helpers
{
    public static class TriangleFactory
    {
        private static IList<Point> CreateTrianglePoints(Panel panel, Triangle triangle, Camera camera)
        {
            if (!IsTriangleVisible(triangle, camera)||triangle.IsDegenerated)
                return null;

            var width = panel.ActualWidth;
            var height = panel.ActualHeight;
            
            //Środek kamery
            var center = camera.Position + camera.Distance * camera.HorizontalDirection;
            //Wsp d
            var d = CalculateDRate(camera);


            var verticalMove = camera.VerticalDirection;
            verticalMove.Normalize();
            var horizontalMove = -Vector3D.CrossProduct(camera.HorizontalDirection, camera.VerticalDirection);
            horizontalMove.Normalize();

            var aP = Converter.ProjectPointToAPlane(
                triangle.A,
                camera.HorizontalDirection.X,
                camera.HorizontalDirection.Y,
                camera.HorizontalDirection.Z,
                d,
                triangle.A - camera.Position);
            var bP = Converter.ProjectPointToAPlane(
                triangle.B,
                camera.HorizontalDirection.X,
                camera.HorizontalDirection.Y,
                camera.HorizontalDirection.Z,
                d,
                triangle.B - camera.Position);
            var cP = Converter.ProjectPointToAPlane(
                triangle.C,
                camera.HorizontalDirection.X,
                camera.HorizontalDirection.Y,
                camera.HorizontalDirection.Z,
                d,
                triangle.C - camera.Position);
            var pA = Converter.CastTo2dPoint(
                aP,
                center,
                horizontalMove,
                verticalMove);
            var pB = Converter.CastTo2dPoint(
                bP,
                center,
                horizontalMove,
                verticalMove);
            var pC = Converter.CastTo2dPoint(
                cP,
                center,
                horizontalMove,
                verticalMove);
            var lineList = new List<Line>();

            pA = TranslatePointToCenter(pA, width, height);
            pB = TranslatePointToCenter(pB, width, height);
            pC = TranslatePointToCenter(pC, width, height);

            return new List<Point>()
                       {
                           pA,pB,pC
                       };
        }

        public static void DrawEmptyTriangles(IEnumerable<Triangle> triangles,Panel panel,Camera camera )
        {

            foreach (var triangle in triangles)
            {
                var points = CreateTrianglePoints(panel, triangle, camera);
                if(points==null)
                    continue;
                
                for(int i=0;i<points.Count();i++)
                {
                    var line = CreateLine(points[i], points[(i + 1)%points.Count()]);
                    panel.Children.Add(line);
                }
            }
        }
        //Płaskie
        public static void CreateFlatTriangle(IEnumerable<Light>lights,double ambientLight,IEnumerable<Triangle> triangles, Panel panel, Camera camera)
        {
            var width = (int)panel.ActualWidth;
            var height = (int)panel.ActualHeight;
            var zBuffer = new double[width, height];
            for (var i = 0; i < height; i++)
                for (var j = 0; j < width; j++)
                    zBuffer[j, i] = double.MaxValue;
            var d = CalculateDRate(camera);

            foreach (var triangle in triangles)
            {
                if (!IsTriangleVisible(triangle, camera) || triangle.IsDegenerated)
                    continue;
                var points = CreateTrianglePoints(panel, triangle, camera);
                if(points==null)
                    continue;
                var drawTriangle = new DrawTriangle(points[0], points[1], points[2]);
                if (lights != null)
                {
                    var color = Converter.CalculateLightColor(lights, ambientLight, triangle.GetNormalVector(),
                                                              triangle.Color);
                    var boundingRect = drawTriangle.GetBoundingRect();
               
                
                    for(var yi = (int)Math.Max(boundingRect.Top, 0); yi < Math.Min(boundingRect.Bottom, height); yi++)
                        for(var xi =(int) Math.Max(boundingRect.Left, 0); xi < Math.Min(boundingRect.Right, width); xi++)
                        {
                            var currPixel = new Point(xi, yi);
                            if (!drawTriangle.Contains(currPixel))
                                continue;
                            double abComp, acComp;
                            if (!drawTriangle.GetVectorComponents(currPixel, out abComp, out acComp))
                                continue;
                            var ab = triangle.B - triangle.A;
                            var ac = triangle.C - triangle.A;
                            var projectedPoint = triangle.A + abComp * ab + acComp * ac; //punkt w przestrzeni, który aktualnie rysujemy
                            var pointProjection = Converter.ProjectPointToAPlane(
                                projectedPoint,
                                camera.HorizontalDirection.X,
                                camera.HorizontalDirection.Y,
                                camera.HorizontalDirection.Z,
                                d,
                                projectedPoint - camera.Position);

                            double dist = (projectedPoint - pointProjection).Length;
                            if (zBuffer[(int)currPixel.X, (int)currPixel.Y] <= dist)
                                continue;
                            zBuffer[(int)currPixel.X,(int)currPixel.Y] = dist;


                            AddPixel(panel,color,currPixel);
                        }
                }



            }
        }
        public static void CreatePhongTriangles(IEnumerable<Light>lights,double ambientLight,IEnumerable<Triangle>triangles,Panel panel,Camera camera )
        {

            var width = (int)panel.ActualWidth;
            var height = (int)panel.ActualHeight;
            var zBuffer = new double[width, height];
            for (var i = 0; i < height; i++)
                for (var j = 0; j < width; j++)
                    zBuffer[j, i] = double.MaxValue;
            var d = CalculateDRate(camera);
            var dictionary = CreatePhongNormals(triangles);
            foreach (var triangle in triangles)
            {
                if (!IsTriangleVisible(triangle, camera) || triangle.IsDegenerated)
                    continue;
                var points = CreateTrianglePoints(panel, triangle, camera);
                if (points == null)
                    continue;
                var drawTriangle = new DrawTriangle(points[0], points[1], points[2]);
                var color = Converter.CalculateLightColor(lights, ambientLight, triangle.GetNormalVector(),
                                                          triangle.Color);
                var boundingRect = drawTriangle.GetBoundingRect();


                for (int yi = (int)Math.Max(boundingRect.Top, 0); yi < Math.Min(boundingRect.Bottom, height); yi++)
                    for (int xi = (int)Math.Max(boundingRect.Left, 0); xi < Math.Min(boundingRect.Right, width); xi++)
                    {
                        Point currPixel = new Point(xi, yi);
                        if (!drawTriangle.Contains(currPixel))
                            continue;
                        double abComp, acComp;
                        if (!drawTriangle.GetVectorComponents(currPixel, out abComp, out acComp))
                            continue;
                        Vector3D ab = triangle.B - triangle.A, ac = triangle.C - triangle.A;
                        Point3D projectedPoint = triangle.A + abComp * ab + acComp * ac; //punkt w przestrzeni, który aktualnie rysujemy
                        Point3D pointProjection = Converter.ProjectPointToAPlane(
                            projectedPoint,
                            camera.HorizontalDirection.X,
                            camera.HorizontalDirection.Y,
                            camera.HorizontalDirection.Z,
                            d,
                            projectedPoint - camera.Position);

                        double dist = (projectedPoint - pointProjection).Length;
                        if (zBuffer[(int)currPixel.X, (int)currPixel.Y] <= dist)
                            continue;
                        zBuffer[(int)currPixel.X, (int)currPixel.Y] = dist;

                        Color aColor = Converter.CalculateLightColor(lights,ambientLight,dictionary[triangle.A], triangle.Color);
                        Color bColor = Converter.CalculateLightColor(lights, ambientLight, dictionary[triangle.B], triangle.Color);
                        Color cColor = Converter.CalculateLightColor(lights, ambientLight, dictionary[triangle.C], triangle.Color);
                        Color pixelColor = Converter.CalculatePhongColor(
                            triangle,
                            projectedPoint,
                            aColor,
                            bColor,
                            cColor);
                        
                        AddPixel(panel, pixelColor, currPixel);
                
                    }

             //   panel.Dispatcher.Invoke(DispatcherPriority.Render, emptyDelegate);

            }
        }
        
        private static Line CreateLine(Point p1, Point p2)
        {
            var line = new Line();
            line.X1 = p1.X;
            line.Y1 = p1.Y;

            line.X2 = p2.X;
            line.Y2 = p2.Y;

            line.Stroke = Settings.Brush;
            line.StrokeThickness = Settings.StrokeThickness;
            return line;
        }
        private static double CalculateDRate(Camera camera)
        {

            var center = camera.Position + camera.Distance * camera.HorizontalDirection;
            return -(	//współczynnik D w równ. płaszczyzny rzutni
                    camera.HorizontalDirection.X * center.X +
                    camera.HorizontalDirection.Y * center.Y +
                    camera.HorizontalDirection.Z * center.Z);

        }
        private static Point TranslatePointToCenter(Point point, double width, double height)
        {
            return new Point(point.X + width / 2, point.Y + height / 2);
        }

        private static bool IsTriangleVisible(Triangle t, Camera camera)
        {
            return (Vector3D.AngleBetween(camera.HorizontalDirection, t.GetNormalVector())) < 90;
        }
        private static void AddPixel(Panel panel,Color color,Point point)
        {
            var rec = new Rectangle();
            Canvas.SetTop(rec, point.Y);
            Canvas.SetLeft(rec, point.X);
            rec.Width = 1;
            rec.Height = 1;
            rec.Fill = new SolidColorBrush(color);
            panel.Children.Add(rec);
        }

        private static IDictionary<Point3D,Vector3D> CreatePhongNormals(IEnumerable<Triangle> triangles )
        {
            var list = new List<Point3D>();
            list.AddRange(triangles.Select(x=>x.A));
            list.AddRange(triangles.Select(x=>x.B));
            list.AddRange(triangles.Select(x=>x.C));
            list = list.Distinct().ToList();
            var dictionary= list.ToDictionary(point3D => point3D, point3D => new Vector3D());

            foreach (var triangle in triangles)
            {
                if(triangle.IsDegenerated)
                    continue;
                Vector3D normal = triangle.GetNormalVector();
                dictionary[triangle.A] += normal;
                dictionary[triangle.B] += normal;
                dictionary[triangle.C] += normal;
            }
            return dictionary;
        }
    }
    public class Point3DEqualityComparer:IEqualityComparer<Point3D>
    {
        private const double EPSILON = 0.00001;

        public bool Equals(Point3D x, Point3D y)
        {
            return Math.Abs(x.X - y.X) < EPSILON && Math.Abs(x.Y - y.Y) < EPSILON && Math.Abs(x.Z - y.Z) < EPSILON;
        }

        public int GetHashCode(Point3D obj)
        {
            throw new NotImplementedException();
        }
    }
}
